package tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import lib.Command;
import lib.Utility;
import lib.messaging.NetworkAddress;
import lib.paxos.AcceptMessage;
import lib.paxos.AcceptorState;
import lib.paxos.AckMessage;
import lib.paxos.DecideMessage;
import lib.paxos.IAcceptorState;
import lib.paxos.PaxosException;
import lib.paxos.PrepareMessage;
import lib.paxos.RoundInfo;
import lib.paxos.SequenceID;
import lib.paxos.SyncMessage;
import lib.rpc.IRPCMethods;
import lib.rpc.ServerRPCMethods;

import org.junit.Test;


/**
 * Unit tests for {@link ServerRPCMethods}
 * @author abhip
 *
 */
public class ServerRPCMethodsTests
{
    private static class Context
    {
        public static Context GetDefault() throws IOException
        {
            return GetDefault(true);
        }
        public static Context GetDefault(boolean ensureTestDirExists) throws IOException
        {
            Context context = new Context();
            context.testDir = TestHelpers.GetTempDirectory(ensureTestDirExists);
            context.clientAddress = new NetworkAddress("localhost", 1001);
            context.serverAddress = new NetworkAddress ("localhost", 1000);
            context.logger = new TestLogger();
            context.otherServerAddresses = new ArrayList<NetworkAddress>();
            context.otherServerAddresses.add(new NetworkAddress ("localhost", 1001));
            context.otherServerAddresses.add(new NetworkAddress ("localhost", 1002));
            context.otherServerAddresses.add(new NetworkAddress ("localhost", 1003));
            context.acceptorState = new AcceptorState(
                    context.serverAddress,
                    context.logger,
                    context.otherServerAddresses);
            context.roundID = 1;

            return context;
        }
        private IAcceptorState acceptorState;
        private NetworkAddress clientAddress;
        private TestLogger logger;
        private List<NetworkAddress> otherServerAddresses;
        private int roundID;
        private NetworkAddress serverAddress;
        private File testDir;

        public IAcceptorState AcceptorState()
        {
            return acceptorState;
        }

        public NetworkAddress ClientAddress()
        {
            return clientAddress;
        }

        public void CreateFile(File file) throws IOException
        {
            File parentDir = file.getParentFile();
            parentDir.mkdirs();
            file.createNewFile();
        }

        public List<String> GetFileContents(File file) throws IOException
        {
            List<String> lines = new ArrayList<String>();

            BufferedReader br = new BufferedReader(new FileReader(file));
            String curLine = null;
            while ((curLine = br.readLine()) != null)
            {
                lines.add(curLine);
            }

            return lines;
        }

        public File GetTestFile(String testFileName, File testDir)
        {
            File serverDir = Utility.GetServerDirectory(testDir, this.serverAddress);
            return new File(serverDir, testFileName);
        }

        public TestLogger Logger()
        {
            return logger;
        }

        public List<NetworkAddress> OtherServerAddresses()
        {
            return otherServerAddresses;
        }

        public int RoundID()
        {
        	return roundID;
        }

        public NetworkAddress ServerAddress()
        {
            return serverAddress;
        }

        public void SetAcceptorState(AcceptorState acceptorState)
        {
            this.acceptorState = acceptorState;
        }

        public File TestDir()
        {
            return testDir;
        }

        public void WriteToFile(File file, String data) throws IOException
        {
            if (!file.exists())
            {
                this.CreateFile(file);
            }

            BufferedWriter bufWriter = new BufferedWriter(new FileWriter(file, true));
            bufWriter.write(data);
            bufWriter.newLine();
            bufWriter.close();
        }
    }

    @Test
    public void Accept_WhenReceivesAccept_AcceptsValue()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 2);
        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                null,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptMsg = new AcceptMessage(
                currentRoundID,
                proposerSeqID,
                command);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Accept(
                context.ClientAddress(),
                context.ServerAddress(),
                acceptMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                acceptMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in accept message",
                acceptMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());

        assertNotNull(
                "Accepted message should not be null",
                ackMsg.AcceptedMessage());
        assertEquals(
                "Accepted message should match the one sent",
                acceptMsg.SequenceID(),
                ackMsg.AcceptedMessage().SequenceID());
    }

    @Test
    public void Accept_WhenReceivesAcceptMsgWithoutPreviousPrepareMsg_AcceptsValue()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                null,
                null,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptMsg = new AcceptMessage(
                currentRoundID,
                proposerSeqID,
                command);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Accept(
                context.ClientAddress(),
                context.ServerAddress(),
                acceptMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                acceptMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in accept message",
                acceptMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());
    }

    @Test
    public void Accept_WhenSequenceIDLessThanWhatAcceptorHasSeen_RejectsValue()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 1);
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 2);
        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                null,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptMsg = new AcceptMessage(
                currentRoundID,
                proposerSeqID,
                command);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Accept(
                context.ClientAddress(),
                context.ServerAddress(),
                acceptMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                acceptMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should remain unchanged",
                acceptorHighestSeenSeqID,
                ackMsg.HighestKnownSeqID());

        assertNull(
                "Accepted message should remain null (i.e. original value)",
                ackMsg.AcceptedMessage());
    }

    @Test
    public void AppendLineToFile_WhenFileDoesNotExist_CreatesFileWithDataInDesiredLocation()
            throws IOException, ClassNotFoundException
    {
        // Arrange:
        Context context = Context.GetDefault(false);
        String testFileName = "test.txt";
        String testData = "Hello! This is test data!!";
        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.serverAddress,
                context.TestDir(),
                new AcceptorState(
                        context.ServerAddress(),
                        context.Logger(),
                        context.OtherServerAddresses()),
                context.Logger());

        // Act:
        rpcMethodsServerSide.AppendLineToFile(
                context.ClientAddress(),
                context.ServerAddress(),
                context.RoundID(),
                testFileName,
                testData);


        // Assert:
        File expectedTestFile = context.GetTestFile(testFileName, context.TestDir());
        assertTrue("File should be created at expected location", expectedTestFile.exists());

        List<String> linesRead = context.GetFileContents(expectedTestFile);
        assertEquals("Test file must have exactly one line", 1, linesRead.size());
        assertEquals("Contents of testFile should match what was written to it", testData, linesRead.get(0));
    }

    @Test
    public void CreateFile_CreatesFileInDesiredLocation()
            throws ClassNotFoundException, IOException
    {
        // Arrange:
        Context context = Context.GetDefault();
        String testFileName = "test.txt";
        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                new AcceptorState(
                        context.ServerAddress(),
                        context.Logger(),
                        context.OtherServerAddresses()),
                context.Logger());

        // Act:
        rpcMethodsServerSide.CreateFile(
                context.ClientAddress(),
                context.ServerAddress(),
                context.RoundID(),
                testFileName);

        // Assert:
        File expectedTestFile = context.GetTestFile(testFileName, context.TestDir());
        assertTrue("File should be created at expected location", expectedTestFile.exists());
    }

    @Test
    public void Decide_WhenReceivesDecideMessage_ExecutesCommandAndUpdatesState()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 2);

        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });
        AcceptMessage acceptedMsg = new AcceptMessage(
                currentRoundID,
                proposerSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMsg,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        DecideMessage decideMsg = new DecideMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Decide(
                context.ClientAddress(),
                context.ServerAddress(),
                decideMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                acceptedMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertEquals(
                "Total number of commands executed should increase",
                1,
                context.AcceptorState().GetTotalNumberOfCommands());
    }

    @Test
    public void GetLineFromFile_GetsLine() throws IOException, ClassNotFoundException
    {
        // Arrange:
        Context context = Context.GetDefault();
        String testFileName = "test.txt";
        String testData = "Hello! This is test data!!";
        File testFile = context.GetTestFile(testFileName, context.TestDir());
        context.WriteToFile(testFile, testData);

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                new AcceptorState(
                        context.ServerAddress(),
                        context.Logger(),
                        context.OtherServerAddresses()),
                context.Logger());

        int testLineNumber = 1;

        // Act:
        String lineRead = rpcMethodsServerSide.GetLineFromFile(
                context.ClientAddress(),
                context.ServerAddress(),
                context.RoundID(),
                testFileName,
                testLineNumber);

        // Assert:
        assertNotNull("Line read should not be null", lineRead);
        assertEquals("Line read should be 1st line in file", testData, lineRead);
    }

    @Test
    public void Prepare_WhenAcceptorInIntialState_StartsNewPaxosRoundOnAcceptor()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);

        context.SetAcceptorState(
                new AcceptorState(
                        context.ServerAddress(),
                        context.Logger(),
                        context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                prepareMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in prepare message",
                prepareMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());
    }

    @Test
    public void Prepare_WhenProposerNotInEpoch_GetsNegativeFeedbackFromAcceptor()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 1);
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptedMessage = new AcceptMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMessage,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                new NetworkAddress("localhost", 2000),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);
        assertFalse("isProposerPartOfEpoch should be false", ackMsg.IsProposerPartOfEpoch());
    }

    @Test
    public void Prepare_WhenProposerRoundIDExceedsAcceptorRoundIDByMoreThanOne_GetsAcceptorState()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 0;
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 1);
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptedMessage = new AcceptMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMessage,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID + 2,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should be unchanged",
                context.acceptorState.CurrentRoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should be unchanged",
                context.acceptorState.HighestKnownSeqID(),
                ackMsg.HighestKnownSeqID());
    }

    @Test
    public void Prepare_WhenProposerRoundIDIsLessThanAcceptorRoundID_GetsMissingRoundInfos()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int completedRoundID1 = 1;
        int completedRoundID2 = 2;
        SequenceID highestSeqID = new SequenceID(context.clientAddress.Port(), 1);
        Command completedCommand1 = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });
        Command completedCommand2 = new Command("fetch", new Serializable[] { 2, "abhip", 1 });
        AcceptMessage acceptedMessage = new AcceptMessage(
                completedRoundID2,
                highestSeqID,
                completedCommand2);
        RoundInfo roundInfo1 = new RoundInfo(completedRoundID1, completedCommand1);
        RoundInfo roundInfo2 = new RoundInfo(completedRoundID2, completedCommand2);
        List<RoundInfo> roundInfos = new ArrayList<RoundInfo>();
        roundInfos.add(roundInfo1);
        roundInfos.add(roundInfo2);

        AcceptorState acceptorState = new AcceptorState(
                context.ServerAddress(),
                completedRoundID2,
                highestSeqID,
                acceptedMessage,
                roundInfos,
                context.Logger(),
                context.OtherServerAddresses());

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                acceptorState,
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                completedRoundID1,
                new SequenceID(context.clientAddress.Port(), 10));

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID should match acceptor's state",
                acceptorState.CurrentRoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match acceptor's state",
                acceptorState.HighestKnownSeqID(),
                ackMsg.HighestKnownSeqID());

        assertNotNull(
                "AcceptedMessage should not be null",
                ackMsg.AcceptedMessage());

        assertNotNull(
                "MissingRoundInfos should not be null",
                ackMsg.MissingRoundInfos());

        assertEquals(
                "MissingRoundInfos size should match expected value",
                ackMsg.MissingRoundInfos().size(),
                acceptorState.GetTotalNumberOfCommands());

        assertEquals(
                "First missingRoundInfo should match expected value",
                acceptorState.GetRoundInfo(1).RoundID(),
                ackMsg.MissingRoundInfos().get(0).RoundID());

        assertEquals(
                "Second missingRoundInfo should match expected value",
                acceptorState.GetRoundInfo(2).RoundID(),
                ackMsg.MissingRoundInfos().get(1).RoundID());
    }

    @Test
    public void Prepare_WhenProposerRoundIDOneMoreThanAcceptorRoundID_GetsAcceptorStateOfNewRound()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 0;
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 1);
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptedMessage = new AcceptMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMessage,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID + 1,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                prepareMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in prepare message",
                prepareMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());

        assertNull("AcceptedMessage should be null, since a new round has just begun", ackMsg.AcceptedMessage());

        assertEquals("MissingRoundInfos should be empty", 0, ackMsg.MissingRoundInfos().size());
    }

    @Test
    public void Prepare_WhenProposerSequenceIDGreaterThanAcceptorHighestSeenSeqID_GetsUpdatedAcceptorState()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 1;
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 1);
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);
        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptedMessage = new AcceptMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMessage,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match prepare message roundID",
                prepareMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in prepare message",
                prepareMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());
    }

    @Test
    public void Prepare_WhenProposerSequenceIDLessThanAcceptorHighestSeenSeqID_GetsUnchangedAcceptorState()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int currentRoundID = 0;
        SequenceID acceptorHighestSeenSeqID = new SequenceID(context.clientAddress.Port(), 2);
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 1);
        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        AcceptMessage acceptedMessage = new AcceptMessage(
                currentRoundID,
                acceptorHighestSeenSeqID,
                command);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                currentRoundID,
                acceptorHighestSeenSeqID,
                acceptedMessage,
                new ArrayList<RoundInfo>(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        PrepareMessage prepareMsg = new PrepareMessage(
                currentRoundID,
                proposerSeqID);

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Prepare(
                context.ClientAddress(),
                context.ServerAddress(),
                prepareMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());

        assertEquals(
                "HighestKnownSeqID should be unchanged",
                context.AcceptorState().HighestKnownSeqID(),
                ackMsg.HighestKnownSeqID());
    }

    @Test
    public void Sync_WhenReceivesMissingRoundInfosInInitialState_ExecutesCommandsAndUpdatesState()
            throws IOException, ClassNotFoundException, PaxosException
    {
        // Arrange:
        Context context = Context.GetDefault();

        int proposerRoundID = 5;
        SequenceID proposerSeqID = new SequenceID(context.clientAddress.Port(), 2);

        Command command = new Command("update", new Serializable[] { 1, "abhip", "Hi Abhip!" });

        List<RoundInfo> missingRoundInfos = new ArrayList<RoundInfo>();
        for (int i = 0; i < proposerRoundID; ++i)
        {
            missingRoundInfos.add(new RoundInfo(i + 1, command));
        }

        SyncMessage syncMsg = new SyncMessage(
                proposerRoundID,
                proposerSeqID,
                missingRoundInfos);

        context.SetAcceptorState(new AcceptorState(
                context.ServerAddress(),
                context.Logger(),
                context.OtherServerAddresses()));

        IRPCMethods rpcMethodsServerSide = new ServerRPCMethods(
                context.ServerAddress(),
                context.TestDir(),
                context.AcceptorState(),
                context.Logger());

        // Act:
        AckMessage ackMsg = rpcMethodsServerSide.Sync(
                context.ClientAddress(),
                context.ServerAddress(),
                syncMsg);

        // Assert:
        assertNotNull("AckMsg should not be null", ackMsg);

        assertEquals(
                "RoundID for acceptor should match sync message roundID",
                syncMsg.RoundID(),
                ackMsg.CurrentRoundID());

        assertNotNull(
                "HighestKnownSeqID should not be null",
                ackMsg.HighestKnownSeqID());
        assertEquals(
                "HighestKnownSeqID should match the one specified in sync message",
                syncMsg.SequenceID(),
                ackMsg.HighestKnownSeqID());

        assertNotNull(
                "AcceptedMessage should not be null",
                ackMsg.AcceptedMessage());

        assertEquals(
                "All missing commands should be executed",
                syncMsg.MissingRoundInfos().size(),
                context.AcceptorState().GetTotalNumberOfCommands());
    }
}
